﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using FLib;
using LibraryGuard.Interfaces;
using LibraryGuard.Interfaces.Configuration;

namespace LibraryGuard.Guards
{
	/// <summary>
	/// Playlist guard - creates playlist with specified files.
	/// </summary>
	public class PlaylistGuard
		: IGuard
	{
		#region Logger
		private static NLog.Logger Logger = NLog.LogManager.GetLogger("PlaylistGuard");
		#endregion

		#region Fields
		private static FLib.Data.NaturalOrder Comparer = new FLib.Data.NaturalOrder();
		private readonly IConfiguration BaseConfig = null;
		private readonly Configuration.PlaylistGuard Config = null;
		#endregion

		#region IGuard Members
		/// <inheritdoc />
		public bool OnlySpecificExtensions
		{
			get { return true; }
		}

		/// <inheritdoc />
		public IActionList ActionList { get; set; }

		/// <inheritdoc />
		public void Run()
		{
			if (!string.IsNullOrWhiteSpace(this.Config.File))
			{
				this.ActionList.Add(new CreatePlaylist(this));
			}
		}

		/// <inheritdoc />
		public void FileSystemEvent(FileSystemEventArgs args, string baseDirectory)
		{
			if (args.ChangeType != WatcherChangeTypes.Changed)
			{
				Logger.Trace("Something changed on the disc, recreate");
				this.Run();
			}
		}
		#endregion

		#region Constructor
		public PlaylistGuard(IConfiguration config, IGuardConfiguration guard)
		{
			Validate.Debug(() => config, v => v.NotNull());
			Validate.Debug(() => guard, v => v.NotNull());

			this.BaseConfig = config;
			this.Config = (Configuration.PlaylistGuard)guard;

			if (string.IsNullOrWhiteSpace(this.Config.File))
				throw new ArgumentException("File for playlist must be specified.");

			Logger.Info("Playlist guard enabled");
		}
		#endregion

		#region Actions
		/// <summary>
		/// Creates playlist from all directories.
		/// </summary>
		private sealed class CreatePlaylist
			: IActionInfo
		{
			#region Private fields
			private readonly PlaylistGuard Parent = null;
			#endregion

			#region IActionInfo Members
			/// <inheritdoc />
			public object ActionId
			{
				get { return this.Parent; }
			}

			/// <inheritdoc />
			public bool IsEdgeAction
			{
				get { return false; }
			}

			/// <inheritdoc />
			public bool Execute(IActionList actionList)
			{
				Logger.Trace("Listing files");
				List<string> files = new List<string>();
				foreach (var ext in this.Parent.BaseConfig.Extensions)
				{
					try
					{
						this.Parent.BaseConfig.Directories.ForEach(d => files.AddRange(Directory.GetFiles(d.FullName, "*." + ext, SearchOption.AllDirectories)));
					}
					catch (Exception ex)
					{
						Logger.WarnException("Cannot list all files, aborting", ex);
						return false;
					}
				}
				files.Sort(Comparer);
				Logger.Debug("{0} entries intended for new playlist", files.Count);
				actionList.Replace(new SavePlaylist(files, this.Parent));
				return true;
			}
			#endregion

			#region Constructor
			public CreatePlaylist(PlaylistGuard parent)
			{
				this.Parent = parent;
			}
			#endregion
		}

		/// <summary>
		/// Saves prepared playlist to file.
		/// </summary>
		private sealed class SavePlaylist
			: IActionInfo
		{
			#region Private fields
			private readonly List<string> FilesToSave = null;
			private readonly PlaylistGuard Parent = null;
			#endregion

			#region IActionInfo Members
			/// <inheritdoc />
			public object ActionId
			{
				get { return this.Parent; }
			}

			/// <inheritdoc />
			public bool IsEdgeAction
			{
				get { return true; }
			}

			/// <inheritdoc />
			public bool Execute(IActionList actionList)
			{
				try
				{
					using (var file = new FileStream(this.Parent.Config.File, FileMode.Create, FileAccess.Write))
					using (var writer = new StreamWriter(file, this.Parent.Config.Unicode ? Encoding.UTF8 : Encoding.ASCII))
					{
						this.FilesToSave.ForEach(writer.WriteLine);
					}
					Logger.Info("Playlist '{0}' with {1} entries saved", this.Parent.Config.File, this.FilesToSave.Count);
				}
				catch (Exception ex)
				{
					Logger.WarnException("Cannot save playlist '{0}'".FormatWith(this.Parent.Config.File), ex);
					return false;
				}
				return true;
			}
			#endregion

			#region Constructor
			public SavePlaylist(List<string> filesToSave, PlaylistGuard parent)
			{
				this.FilesToSave = filesToSave;
				this.Parent = parent;
			}
			#endregion

			#region ToString override
			public override string ToString()
			{
				return "Save playlist with {0} entries to file '{1}'".FormatWith(this.FilesToSave.Count, this.Parent.Config.File);
			}
			#endregion
		}
		#endregion
	}
}
